package com.palominolabs.jersey.newrelic; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Singleton; import com.google.inject.servlet.GuiceFilter; import com.google.inject.servlet.ServletModule; import com.google.inject.util.Modules; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.Response; import com.palominolabs.config.ConfigModuleBuilder; import com.palominolabs.servlet.newrelic.NewRelicUnmappedThrowableFilter; import com.sun.jersey.api.core.ResourceConfig; import com.sun.jersey.guice.JerseyServletModule; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.bridge.SLF4JBridgeHandler; import javax.annotation.Nullable; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.LogManager; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class FullStackTest { private static final int PORT = 18080; private Server server; private AsyncHttpClient httpClient; private StubNewRelicWrapper wrapper; @Before public void setUp() throws Exception { LogManager.getLogManager().reset(); SLF4JBridgeHandler.install(); wrapper = new StubNewRelicWrapper(); httpClient = new AsyncHttpClient(); } @After public void tearDown() throws Exception { server.stop(); } @Test public void testInvokeResourceWithCategory() throws Exception { final Map<String, String> initParams = new HashMap<>(); initParams.put(NewRelicResourceFilterFactory.TRANSACTION_CATEGORY_PROP, "someCategory"); initParams.put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, NewRelicResourceFilterFactory.class.getCanonicalName()); server = getServer(getInjector(initParams).getInstance(GuiceFilter.class)); server.start(); Response response = httpClient.prepareGet("http://localhost:" + PORT + "/foo").execute().get(); assertEquals(200, response.getStatusCode()); assertEquals(newArrayList("someCategory:/foo GET"), wrapper.getNames()); assertTrue(wrapper.getThrowables().isEmpty()); } @Test public void testInvokeResourceWithoutCategory() throws Exception { final Map<String, String> initParams = new HashMap<>(); initParams.put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, NewRelicResourceFilterFactory.class.getCanonicalName()); server = getServer(getInjector(initParams).getInstance(GuiceFilter.class)); server.start(); Response response = httpClient.prepareGet("http://localhost:" + PORT + "/foo").execute().get(); assertEquals(200, response.getStatusCode()); assertEquals(newArrayList("/foo GET"), wrapper.getNames()); assertTrue(wrapper.getThrowables().isEmpty()); } @Test public void testInvokeThrowsResource() throws Exception { final Map<String, String> initParams = new HashMap<>(); initParams.put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, NewRelicResourceFilterFactory.class.getCanonicalName()); server = getServer(getInjector(initParams).getInstance(GuiceFilter.class)); server.start(); Response response = httpClient.prepareGet("http://localhost:" + PORT + "/foo/throw").execute().get(); assertEquals(500, response.getStatusCode()); assertEquals(newArrayList("/foo/throw GET"), wrapper.getNames()); assertEquals(1, wrapper.getThrowables().size()); assertEquals("zomg", wrapper.getThrowables().get(0).getMessage()); } @Test public void testInvokeThrowsMappedResource() throws Exception { final Map<String, String> initParams = new HashMap<>(); initParams.put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, NewRelicResourceFilterFactory.class.getCanonicalName()); server = getServer(getInjector(initParams).getInstance(GuiceFilter.class)); server.start(); Response response = httpClient.prepareGet("http://localhost:" + PORT + "/foo/throwMapped").execute().get(); assertEquals(javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatusCode()); assertEquals(newArrayList("/foo/throwMapped GET"), wrapper.getNames()); assertEquals(1, wrapper.getThrowables().size()); assertEquals("asdf", wrapper.getThrowables().get(0).getMessage()); } private Injector getInjector(final Map<String, String> initParams) { return Guice.createInjector(new AbstractModule() { @Override protected void configure() { binder().requireExplicitBindings(); install(new ServletModule() { @Override protected void configureServlets() { bind(GuiceContainer.class); bind(NewRelicUnmappedThrowableFilter.class); serve("/*").with(GuiceContainer.class, initParams); filter("/*").through(NewRelicUnmappedThrowableFilter.class); } }); install(new JerseyServletModule()); install(Modules.override(new JerseyNewRelicModule()).with(new AbstractModule() { @Override protected void configure() { bind(NewRelicWrapper.class).toInstance(wrapper); } })); bind(GuiceFilter.class); bind(SomeResource.class); bind(SampleExceptionMapper.class); install(new ConfigModuleBuilder().build()); } }); } private Server getServer(GuiceFilter filter) { Server server = new Server(PORT); ServletContextHandler servletHandler = new ServletContextHandler(); servletHandler.addServlet(new ServletHolder(new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setContentType("text/plain"); resp.setContentType("UTF-8"); resp.getWriter().append("404"); } }), "/*"); // add guice servlet filter servletHandler.addFilter(new FilterHolder(filter), "/*", EnumSet.allOf(DispatcherType.class)); server.setHandler(servletHandler); return server; } @Path("foo") public static class SomeResource { @GET public String get() { return "foo"; } @GET @Path("throw") public String getThrow() { throw new RuntimeException("zomg"); } @GET @Path("throwMapped") public String getThrowMapped() throws SampleException { throw new SampleException("asdf"); } } static class StubNewRelicWrapper implements NewRelicWrapper { List<Throwable> throwables = new ArrayList<>(); List<String> names = new ArrayList<>(); @Override public synchronized void noticeError(Throwable t) { throwables.add(t); } @Override public synchronized void setTransactionName(@Nullable String category, String transactionName) { names.add(category == null ? transactionName : (category + ":" + transactionName)); } synchronized List<String> getNames() { return names; } synchronized List<Throwable> getThrowables() { return throwables; } } static class SampleException extends Exception { public SampleException(String message) { super(message); } } @Provider @Singleton static class SampleExceptionMapper implements ExceptionMapper<SampleException> { @Override public javax.ws.rs.core.Response toResponse(SampleException exception) { return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE).build(); } } }